﻿using System;
using System.Runtime.InteropServices;

namespace Microsoft.Xna.Framework
{
    /// <summary>
    /// Defines a ray.
    /// </summary>
    [Serializable]
	[StructLayout(LayoutKind.Sequential)]
    public struct Ray : IEquatable<Ray>
    {
        #region Public Fields
        /// <summary>
        /// Unit vector specifying the direction the Ray is pointing.
        /// </summary>
        public Vector3 Direction;
        /// <summary>
        /// Specifies the starting point of the Ray.
        /// </summary>
        public Vector3 Position;
        #endregion

        #region Constructor
        /// <summary>
        /// Creates a new instance of Ray.
        /// </summary>
        /// <param name="position">The starting point of the Ray.</param>
        /// <param name="direction">Unit vector describing the direction of the Ray.</param>
        public Ray(Vector3 position, Vector3 direction)
        {
            Position = position;
            Direction = direction;
        } 
        #endregion

        #region Public Methods
        /// <summary>
        /// Determines whether the specified object is equal to the current Ray.
        /// </summary>
        /// <param name="obj">The Object to compare with the current Ray.</param>
        public override bool Equals(object obj)
        {
            return (obj is Ray) ? this.Equals((Ray)obj) : false;
        }
        /// <summary>
        /// Determines whether two instances of Ray are equal.
        /// </summary>
        /// <param name="other">The Ray to compare with the current Ray.</param>
        public bool Equals(Ray other)
        {
            return this.Position.Equals(other.Position) && this.Direction.Equals(other.Direction);
        }
        /// <summary>
        /// Gets the hash code for this instance.
        /// </summary>
        public override int GetHashCode()
        {
            return Position.GetHashCode() ^ Direction.GetHashCode();
        }
        /// <summary>
        /// Checks whether the current Ray intersects a BoundingSphere.
        /// </summary>
        /// <param name="sphere">The BoundingSphere to check for intersection with.</param>
        /// <param name="result">Distance at which the ray intersects the BoundingSphere or null if there is no intersection.</param>
        public void Intersects(ref BoundingSphere sphere, out float? result)
        {
            // Find the vector between where the ray starts the the sphere's centre 
            Vector3 difference = sphere.Center - this.Position;

            float differenceLengthSquared = difference.LengthSquared();
            float sphereRadiusSquared = sphere.Radius * sphere.Radius;

            float distanceAlongRay;

            // If the distance between the ray start and the sphere's centre is less than 
            // the radius of the sphere, it means we've intersected. N.B. checking the LengthSquared is faster. 
            if (differenceLengthSquared < sphereRadiusSquared)
            {
                result = 0.0f;
                return;
            }

            Vector3.Dot(ref this.Direction, ref difference, out distanceAlongRay);
            // If the ray is pointing away from the sphere then we don't ever intersect 
            if (distanceAlongRay < 0)
            {
                result = null;
                return;
            }

            // Next we kinda use Pythagoras to check if we are within the bounds of the sphere 
            // if x = radius of sphere 
            // if y = distance between ray position and sphere centre 
            // if z = the distance we've travelled along the ray 
            // if x^2 + z^2 - y^2 < 0, we do not intersect 
            float dist = sphereRadiusSquared + distanceAlongRay * distanceAlongRay - differenceLengthSquared;

            result = (dist < 0) ? null : distanceAlongRay - (float?)Math.Sqrt(dist);
        }
        /// <summary>
        /// Checks whether the current Ray intersects a BoundingSphere.
        /// </summary>
        /// <param name="sphere">The BoundingSphere to check for intersection with.</param>
        public float? Intersects(BoundingSphere sphere)
        {
            float? result;
            Intersects(ref sphere, out result);
            return result;
        }
        /// <summary>
        /// Determines whether this Ray intersects a specified Plane.
        /// </summary>
        /// <param name="plane">The Plane with which to calculate this Ray's intersection.</param>
        /// <param name="result">The distance at which this Ray intersects the specified Plane, or null if there is no intersection.</param>
        public void Intersects(ref Plane plane, out float? result)
        {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Determines whether this Ray intersects a specified Plane.
        /// </summary>
        /// <param name="plane">The Plane with which to calculate this Ray's intersection.</param>
        public float? Intersects(Plane plane)
        {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Checks whether the Ray intersects a specified BoundingFrustum.
        /// </summary>
        /// <param name="frustum">The BoundingFrustum to check for intersection with the Ray.</param>
        public float? Intersects(BoundingFrustum frustum)
        {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Checks whether the current Ray intersects a BoundingBox.
        /// </summary>
        /// <param name="box">The BoundingBox to check for intersection with.</param>
        /// <param name="result">Distance at which the ray intersects the BoundingBox or null if there is no intersection.</param>
        public void Intersects(ref BoundingBox box, out float? result)
        {
            result = Intersects(box);
        }
        /// <summary>
        /// Checks whether the current Ray intersects a BoundingBox.
        /// </summary>
        /// <param name="box">The BoundingBox to check for intersection with.</param>
        public float? Intersects(BoundingBox box)
        {
            //first test if start in box 
            if (Position.X >= box.Min.X
                    && Position.X <= box.Max.X
                    && Position.Y >= box.Min.Y
                    && Position.Y <= box.Max.Y
                    && Position.Z >= box.Min.Z
                    && Position.Z <= box.Max.Z)
                return 0.0f;// here we concidere cube is full and origine is in cube so intersect at origine 

            //Second we check each face 
            Vector3 maxT = new Vector3(-1.0f);
            //Vector3 minT = new Vector3(-1.0f); 
            //calcul intersection with each faces 
            if (Position.X < box.Min.X && Direction.X != 0.0f)
                maxT.X = (box.Min.X - Position.X) / Direction.X;
            else if (Position.X > box.Max.X && Direction.X != 0.0f)
                maxT.X = (box.Max.X - Position.X) / Direction.X;
            if (Position.Y < box.Min.Y && Direction.Y != 0.0f)
                maxT.Y = (box.Min.Y - Position.Y) / Direction.Y;
            else if (Position.Y > box.Max.Y && Direction.Y != 0.0f)
                maxT.Y = (box.Max.Y - Position.Y) / Direction.Y;
            if (Position.Z < box.Min.Z && Direction.Z != 0.0f)
                maxT.Z = (box.Min.Z - Position.Z) / Direction.Z;
            else if (Position.Z > box.Max.Z && Direction.Z != 0.0f)
                maxT.Z = (box.Max.Z - Position.Z) / Direction.Z;

            //get the maximum maxT 
            if (maxT.X > maxT.Y && maxT.X > maxT.Z)
            {
                if (maxT.X < 0.0f)
                    return null;// ray go on opposite of face 
                //coordonate of hit point of face of cube 
                float coord = Position.Z + maxT.X * Direction.Z;
                // if hit point coord ( intersect face with ray) is out of other plane coord it miss  
                if (coord < box.Min.Z || coord > box.Max.Z)
                    return null;
                coord = Position.Y + maxT.X * Direction.Y;
                if (coord < box.Min.Y || coord > box.Max.Y)
                    return null;
                return maxT.X;
            }
            if (maxT.Y > maxT.X && maxT.Y > maxT.Z)
            {
                if (maxT.Y < 0.0f)
                    return null;// ray go on opposite of face 
                //coordonate of hit point of face of cube 
                float coord = Position.Z + maxT.Y * Direction.Z;
                // if hit point coord ( intersect face with ray) is out of other plane coord it miss  
                if (coord < box.Min.Z || coord > box.Max.Z)
                    return null;
                coord = Position.X + maxT.Y * Direction.X;
                if (coord < box.Min.X || coord > box.Max.X)
                    return null;
                return maxT.Y;
            }
            else //Z 
            {
                if (maxT.Z < 0.0f)
                    return null;// ray go on opposite of face 
                //coordonate of hit point of face of cube 
                float coord = Position.X + maxT.Z * Direction.X;
                // if hit point coord ( intersect face with ray) is out of other plane coord it miss  
                if (coord < box.Min.X || coord > box.Max.X)
                    return null;
                coord = Position.Y + maxT.Z * Direction.Y;
                if (coord < box.Min.Y || coord > box.Max.Y)
                    return null;
                return maxT.Z;
            }
        }
        /// <summary>
        /// Returns a String that represents the current Ray.
        /// </summary>
        public override string ToString()
        {
            return string.Format("{{Position:{0} Direction:{1}}}", Position.ToString(), Direction.ToString());
        } 
        #endregion

        #region Operators
        /// <summary>
        /// Determines whether two instances of Ray are not equal.
        /// </summary>
        /// <param name="a">The object to the left of the inequality operator.</param>
        /// <param name="b">The object to the right of the inequality operator.</param>
        public static bool operator !=(Ray a, Ray b)
        {
            return !a.Equals(b);
        }
        /// <summary>
        /// Determines whether two instances of Ray are equal.
        /// </summary>
        /// <param name="a">The object to the left of the equality operator.</param>
        /// <param name="b">The object to the right of the equality operator.</param>
        public static bool operator ==(Ray a, Ray b)
        {
            return a.Equals(b);
        } 
        #endregion
    }
}
